/*
 * Copyright Notice. Copyright 2011 Northwestern University and Stanford
 * University (“caBIG® Participant”). AIM Template Enterprise Service conforms
 * to caBIG® technical specifications and is part of the caBIG® initiative. The
 * software subject to this notice and license includes human readable source
 * code form, machine readable, binary, object code form and related
 * documentation (the “caBIG® Software”).
 *
 * This caBIG® Software License (the “License”) is between the National Cancer
 * Institute (NCI) and You. “You (or “Your”) shall mean a person or an entity,
 * and all other entities that control, are controlled by, or are under common
 * control with the entity. “Control” for purposes of this definition means (i)
 * the direct or indirect power to cause the direction or management of such
 * entity, whether by contract or otherwise, or (ii) ownership of fifty percent
 * (50%) or more of the outstanding shares, or (iii) beneficial ownership of
 * such entity.
 *
 * Provided that You agree to the conditions described below, NCI grants You a
 * non-exclusive, worldwide, perpetual, fully-paid-up, no-charge, irrevocable,
 * transferable and royalty-free right and license in its rights in the caBIG®
 * Software, including any copyright or patent rights therein, to (i) use,
 * install, disclose, access, operate, execute, reproduce, copy, modify,
 * translate, market, publicly display, publicly perform, and prepare
 * derivative works of the caBIG® Software in any manner and for any purpose,
 * and to have or permit others to do so; (ii) make, have made, use, practice,
 * sell, and offer for sale, import, and/or otherwise dispose of caBIG®
 * Software (or portions thereof); (iii) distribute and have distributed to and
 * by third parties the caBIG® Software and any modifications and derivative
 * works thereof; and (iv) sublicense the foregoing rights set out in (i), (ii)
 * and (iii) to third parties, including the right to license such rights to
 * further third parties. For sake of clarity, and not by way of limitation,
 * NCI shall have no right of accounting or right of payment from You or Your
 * sublicensees for the rights granted under this License. This License is
 * granted at no charge to You. Your downloading, copying, modifying,
 * displaying, distributing or use of caBIG® Software constitutes acceptance of
 * all of the terms and conditions of this Agreement. If you do not agree to
 * such terms and conditions, you have no right to download, copy, modify,
 * display, distribute or use the caBIG® Software.
 *
 * Your redistributions of the source code for the caBIG® Software must retain
 * the above copyright notice, this list of conditions and the disclaimer and
 * limitation of liability of Article 6 below. Your redistributions in object
 * code form must reproduce the above copyright notice, this list of conditions
 * and the disclaimer of Article 6 in the documentation and/or other materials
 * provided with the distribution, if any.
 *
 * Your end-user documentation included with the redistribution, if any, must
 * include the following acknowledgment: “This product includes software
 * developed by Danny Korenblum, Alan Snyder, Vitaliy Semeshko, Vladimir Kleper
 * and Pattanasak Mongkolwat, Northwestern University and Stanford University.”
 * If You do not include such end-user documentation, You shall include this
 * acknowledgment in the caBIG® Software itself, wherever such third-party
 * acknowledgments normally appear.
 *
 * You may not use the names “Northwestern University”, “Stanford University”,
 * “The National Cancer Institute”, “NCI”, “Cancer Bioinformatics Grid” or
 * “caBIG®” to endorse or promote products derived from this caBIG® Software.
 * This License does not authorize You to use any trademarks, service marks,
 * trade names, logos or product names of either caBIG® Participant, NCI or
 * caBIG®, except as required to comply with the terms of this License.
 *
 * For sake of clarity, and not by way of limitation, You are not prohibited by
 * this License from incorporating this caBIG® Software into Your proprietary
 * programs and into any third party proprietary programs. However, if You
 * incorporate the caBIG® Software into third party proprietary programs, You
 * agree that You are solely responsible for obtaining any permission from such
 * third parties required to incorporate the caBIG® Software into such third
 * party proprietary programs and for informing Your sublicensees, including
 * without limitation Your end-users, of their obligation to secure any
 * required permissions from such third parties before incorporating the caBIG®
 * Software into such third party proprietary software programs. In the event
 * that You fail to obtain such permissions, You agree to indemnify NCI for any
 * claims against NCI by such third parties, except to the extent prohibited by
 * law, resulting from Your failure to obtain such permissions.
 * For sake of clarity, and not by way of limitation, You may add Your own
 * copyright statement to Your modifications and to the derivative works, and
 * You may provide additional or different license terms and conditions in
 * Your sublicenses of modifications of the caBIG® Software, or any derivative
 * works of the caBIG® Software as a whole, provided Your use, reproduction,
 * and distribution of the Work otherwise complies with the conditions stated
 * in this License.
 *
 * THIS caBIG® SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE) ARE
 * DISCLAIMED. IN NO EVENT SHALL NCI OR THE NORTHWESTERN UNIVERSITY OR ITS
 * AFFILIATES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS caBIG® SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package edu.stanford.isis.lexicon.tools.converter.radlex;

import java.io.FileReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import au.com.bytecode.opencsv.CSVReader;
import edu.stanford.isis.lexicon.tools.converter.Converter;
import edu.stanford.isis.lexicon.tools.converter.LexiconTerm;
import edu.stanford.isis.lexicon.tools.db.DatabaseManager;

/**
 * @author Vitaliy Semeshko
 */

@Component(RadlexConverter.NAME)
public class RadlexConverter implements Converter {

	public static final String NAME = "RadlexConverter";
	private static final String PATH = "src/main/resources/radlex/RadLexParentRel_PartOf_Sep262011_UTF8.txt";
	private static final char SEPARATOR = '\t';

	private static final String PART_OF_SFX = "_PartOf";
	
	private Map<String, LexiconTerm> terms = new HashMap<String, LexiconTerm>();
	private Set<String> uniqueFilter = new HashSet<String>();
	private List<String> noParentDefinedTerms = new ArrayList<String>();

	public static final String ORPHAN_ROOT = "RID_ORPHAN_ROOT"; 
	private Integer orphanId;

//	private Set<String> orphanIgnoreSet = new HashSet<String>() {{
//		add("RID29152");
//		add("RID29043");
//		add("RID28638");
//	}};
	
//	private Set<String> importIgnoreSet = new HashSet<String>() {{
//		add("RID0");
//		add("RID15849");
//		add("RID1");
//	}};
	
	@Autowired
	private DatabaseManager databaseManager;

	@Transactional
	public int convert() {
		System.out.println("Inserting...");
		int rows = insert();

		if (!noParentDefinedTerms.isEmpty()) {
			System.out.println("Terms with no parent defined (neither IsA nor PartOf)");
			for (String s : noParentDefinedTerms) {
				System.out.println(s);
			}
			System.out.println("Number of terms with no parent defined: " + noParentDefinedTerms.size());
		}

		System.out.println("Updating relations...");
		updateRelations();

		return rows;
	}

	private int insert() {
		Date creationDate = new Date();

		boolean lexiconAdded = false;
		String termTable = null;
		int lineNumber = 0;
		try {
			CSVReader reader = new CSVReader(new FileReader(PATH), SEPARATOR);

			String [] nextLine;
			while ((nextLine = reader.readNext()) != null) {

				// skip empty lines
				if (nextLine.length < 4) {
					System.out.println("Empty line: " + lineNumber);
					continue;
				}

				String codeMeaning = nextLine[0];
				String codeValue = nextLine[1];
				String schemaDesignator = nextLine[2];
				String description = nextLine[3];

				// ignore some nodes...
//				if (importIgnoreSet.contains(codeValue)) {
//					continue;
//				}
				
				String parentIsA = (nextLine.length >= 5 ? nextLine[4] : "").trim();
				String parentPartOf = (nextLine.length >= 6 ? nextLine[5] : "").trim();
				// trim parents to null
				parentIsA = parentIsA.isEmpty() ? null : parentIsA;
				parentPartOf = parentPartOf.isEmpty() ? null : parentPartOf;

				if (!lexiconAdded) {
					termTable = addLexicon(schemaDesignator, creationDate);
					lexiconAdded = true;

					//TODO: this is temporary
					// add orphan root
					//orphanId = databaseManager.addTerm(termTable, "Orphans (temporary)", ORPHAN_ROOT, null, null, creationDate);
				}

				LexiconTerm term = new LexiconTerm();
				term.setCodeMeaning(codeMeaning);
				term.setCodeValue(codeValue);
				term.setDescription(description);
				term.setParent(parentIsA);
				term.setTableName(termTable);

				// is a child
				LexiconTerm termIsA = null;
				if (parentIsA != null) {
					termIsA = new LexiconTerm();
					termIsA.setCodeMeaning(codeMeaning);
					termIsA.setCodeValue(codeValue);
					termIsA.setDescription(description);
					termIsA.setParent(parentIsA);
					termIsA.setTableName(termTable);
				}

				// part of child (if defined and not equals to parentIsA)
				LexiconTerm termPartOf = null;
				if (parentPartOf != null && (parentIsA == null || !parentIsA.equals(parentPartOf))) {
					termPartOf = new LexiconTerm();
					termPartOf.setCodeMeaning(codeMeaning);
					termPartOf.setCodeValue(codeValue);
					termPartOf.setDescription(description);
					termPartOf.setParent(parentPartOf);
					termPartOf.setTableName(termTable);
				}

				if (parentIsA == null && parentPartOf == null) {
					noParentDefinedTerms.add("No parent defined for term: " + codeValue + " " + codeMeaning);
				}

				if (lexiconAdded) {
					// filter uniqueness by code value and description
					if (!uniqueFilter.contains(codeValue + description)) {

						lineNumber++;

						if (termIsA != null) {
							Integer id = databaseManager.addTerm(termIsA.getTableName(),
									termIsA.getCodeMeaning(),
									termIsA.getCodeValue(),
									termIsA.getDescription(),
									null,
									creationDate);
							termIsA.setId(id);
							terms.put(codeValue, termIsA);
						}

						if (termPartOf != null) {
							Integer id = databaseManager.addTerm(termPartOf.getTableName(),
									termPartOf.getCodeMeaning(),
									termPartOf.getCodeValue(),
									termPartOf.getDescription(),
									null,
									creationDate);
							termPartOf.setId(id);
							terms.put(codeValue + PART_OF_SFX, termPartOf);
						}

						// if no parent defined we still need to import (may be root node)
						if (termIsA == null && termPartOf == null) {
							Integer id = databaseManager.addTerm(term.getTableName(),
									term.getCodeMeaning(),
									term.getCodeValue(),
									term.getDescription(),
									null,
									creationDate);
							term.setId(id);
							terms.put(codeValue, term);
						}

						uniqueFilter.add(codeValue + description);
					} else {
						System.out.println("Duplicated term: " + codeValue + " " + description);
					}
				}

				if ((lineNumber % 200) == 0) {
					System.out.println(lineNumber + " rows");
				}
			}
			return lineNumber;
		} catch (Exception e) {
			throw new RuntimeException("Failed line: " + lineNumber, e);
		}		
	}

	private void updateRelations() {
		boolean hideOrphan = true;
		
		int withNoParent = 0;
		for (LexiconTerm term : terms.values()) {
			LexiconTerm parent = terms.get(term.getParent());
			if (parent == null) {
				parent = terms.get(term.getParent() + PART_OF_SFX);
			}

			if (parent != null) {
				Integer parentId = parent.getId();
				if (parentId != null) {
					databaseManager.setParent(term.getTableName(), term.getId(), parentId);
				} else {
					withNoParent++;
					System.out.println("ID is empty for term: " + parent.getCodeValue());	
				}
			} else {
				withNoParent++;
				System.out.println("No parent found for term: " + term.getCodeValue() + " " + term.getCodeMeaning());

				//TODO: this is temporary
				// add to orphan root (if not manually defined root nodes)
//				if (!orphanIgnoreSet.contains(term.getCodeValue())) {
//					databaseManager.setParent(term.getTableName(), term.getId(), orphanId);
//				}
			}
			
			//TEMPORARY: hide orphans
//			if (hideOrphan) {
//				databaseManager.setParent(term.getTableName(), orphanId, -1);
//				hideOrphan = false;
//			}
//			
		}

		System.out.println("Number of terms with no parent found: " + withNoParent);
	}

	private String addLexicon(String schemaDesignator, Date creationDate) {
		return databaseManager.addLexicon("Radlex", schemaDesignator, "1.0", "Radlex Lexicon", "Radlex", true, creationDate);
	}

}
